﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Threading.Tasks;

namespace IndexedList
{
    public class IndexedList<T> : List<T>, IIndexedList<T> where T : new()
    {
        IndexCollection<T> Indexes = new IndexCollection<T>();
        FieldValueResolver fieldValueResolver = new FieldValueResolver();

        public bool IgnoreNulls
        {
            get;
            set;
        }

        public IndexedList()
        {
        }

        public IndexedList(IListIndex<T> index)
            : this()
        {
            AddIndex(index);
        }

        public void AddIndex(IListIndex<T> index)
        {
            Indexes.Add(index);
            index.IndexList(this);
        }

        public void Clear()
        {
            base.Clear();
            foreach (var index in this.Indexes)
            {
                index.ClearIndexData();
            }
        }

        public void Add(T item)
        {
            base.Add(item);
            foreach (var index in this.Indexes)
            {
                index.IndexItem(item);
            }
        }

        public void Remove(T item)
        {
            base.Remove(item);
            foreach (var index in this.Indexes)
            {
                index.RemoveIndex(item);
            }
        }

        public IEnumerable<T> Find(Expression<Func<T, bool>> expression)
        {
            IEnumerable<T> res = new List<T>();
            fieldValueResolver.IgnoreNulls = this.IgnoreNulls;
            Dictionary<string, object> fieldValues = fieldValueResolver.GetFieldValues(expression);

            IListIndex<T> index = Indexes.GetIndexByFields(fieldValues.Keys);

            if (index != null)
            {
                res = index.FindItems(fieldValues);
            }
            else
            {
                res = this.Where<T>(expression.Compile()).ToList();
            }

            return res;
        }

        public T FindSingle(Expression<Func<T, bool>> expression)
        {
            T res = default(T);
            fieldValueResolver.IgnoreNulls = this.IgnoreNulls;
            Dictionary<string, object> fieldValues = fieldValueResolver.GetFieldValues(expression);

            IListIndex<T> index = Indexes.GetIndexByFields(fieldValues.Keys);

            if (index != null)
            {
                res = index.FindSingleItem(fieldValues);
            }
            else
            {
                res = this.Where<T>(expression.Compile()).SingleOrDefault();
            }

            return res;
        }

        public IEnumerable<T> FindUsingIndex(string indexName, T item)
        {
            IEnumerable<T> res = new List<T>();
            IListIndex<T> index = this.Indexes.GetIndexByName(indexName);
            if (index != null)
            {
                res = index.FindItems(item);
            }
            else
            {
                throw new Exception("Cannot find any index with specified name.");
            }
            return res;
        }

        public T FindSingleUsingIndex(string indexName, T item)
        {
            T res = default(T);
            IListIndex<T> index = this.Indexes.GetIndexByName(indexName);
            if (index != null)
            {
                res = index.FindSingleItem(item);
            }
            else
            {
                throw new Exception("Cannot find any index with specified name.");
            }

            return res;
        }

        public IEnumerable<T> FindUsingIndex(string indexName, params object[] values)
        {
            IEnumerable<T> res = new List<T>();
            IListIndex<T> index = this.Indexes.GetIndexByName(indexName);
            if (index != null)
            {
                res = index.FindItems(values);
            }
            else
            {
                throw new Exception("Cannot find any index with specified name.");
            }
            return res;
        }

        public T FindSingleUsingIndex(string indexName, params object[] values)
        {
            T res = default(T);
            IListIndex<T> index = this.Indexes.GetIndexByName(indexName);
            if (index != null)
            {
                res = index.FindSingleItem(values);
            }
            else
            {
                throw new Exception("Cannot find any index with specified name.");
            }
            return res;
        }

        public IEnumerable<T> Find(params object[] values)
        {
            IEnumerable<T> res = new List<T>();
            IListIndex<T> index = this.Indexes.GetIndexByFieldValues(values);
            if (index != null)
            {
                res = index.FindItems(values);
            }
            else
            {
                throw new Exception("Cannot find any index with specified field values.");
            }
            return res;
        }

        public T FindSingle(params object[] values)
        {
            T res = default(T);
            IListIndex<T> index = this.Indexes.GetIndexByFieldValues(values);
            if (index != null)
            {
                res = index.FindSingleItem(values);
            }
            else
            {
                throw new Exception("Cannot find any index with specified field values.");
            }
            return res;
        }
    }

}